﻿Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports System.Globalization
Imports System.IO
Imports System.Reflection
Imports System.Runtime.Remoting.Messaging
Imports System.Runtime.Remoting.Proxies
Imports System.Runtime.Serialization
Imports System.Runtime.Serialization.Formatters.Binary
Imports System.Threading

Namespace System.Workflow.Activities
    <Serializable> _
    Friend NotInheritable Class MethodMessage
        Implements IMethodMessage, IMessage, IMethodResponseMessage
        ' Methods
        Friend Sub New(ByVal interfaceType As Type, ByVal methodName As String, ByVal args As Object(), ByVal identity As String)
            Me.New(interfaceType, methodName, args, identity, False)
        End Sub

        Friend Sub New(ByVal interfaceType As Type, ByVal methodName As String, ByVal args As Object(), ByVal identity As String, ByVal responseRequired As Boolean)
            Me.interfaceType = interfaceType
            Me.methodName = methodName
            Me.args = args
            Me.callContext = MethodMessage.GetLogicalCallContext
            If responseRequired Then
                Me.returnValueSignalEvent = New ManualResetEvent(False)
            End If
            Me.PopulateIdentity(Me.callContext, identity)
            Me.Clone
        End Sub

        Private Function Clone() As Object
            Dim objArray As Object() = New Object(Me.args.Length  - 1) {}
            Dim i As Integer
            For i = 0 To Me.args.Length - 1
                objArray(i) = Me.Clone(Me.args(i))
            Next i
            Me.clonedArgs = objArray
            Return objArray
        End Function

        Private Function Clone(ByVal source As Object) As Object
            If ((source Is Nothing) OrElse source.GetType.IsValueType) Then
                Return source
            End If
            Dim cloneable As ICloneable = [TryCast](source,ICloneable)
            If (Not cloneable Is Nothing) Then
                Return cloneable.Clone
            End If
            Dim formatter As New BinaryFormatter
            Dim serializationStream As New MemoryStream(&H400)
            Try 
                formatter.Serialize(serializationStream, source)
            Catch exception As SerializationException
                Throw New InvalidOperationException(SR.GetString("Error_EventArgumentSerializationException"), exception)
            End Try
            serializationStream.Position = 0
            Return formatter.Deserialize(serializationStream)
        End Function

        Private Shared Function GetLogicalCallContext() As LogicalCallContext
            SyncLock MethodMessage.syncObject
                If (MethodMessage.singletonCallContext Is Nothing) Then
                    Dim proxy As New CallContextProxy(GetType(IDisposable))
                    DirectCast(proxy.GetTransparentProxy, IDisposable).Dispose
                    MethodMessage.singletonCallContext = proxy.CallContext
                End If
                Return [TryCast](MethodMessage.singletonCallContext.Clone,LogicalCallContext)
            End SyncLock
        End Function

        <OnDeserialized> _
        Private Sub OnDeserialized(ByVal context As StreamingContext)
            If (Me.callbackCookie <> Guid.Empty) Then
                SyncLock MethodMessage.syncRoot
                    If MethodMessage.staticMethodMessageMap.TryGetValue(Me.callbackCookie, Me.previousMessage) Then
                        MethodMessage.staticMethodMessageMap.Remove(Me.callbackCookie)
                    End If
                End SyncLock
                If (Not Me.previousMessage Is Nothing) Then
                    Me.responseSet = Me.previousMessage.responseSet
                    Me.returnValueSignalEvent = Me.previousMessage.returnValueSignalEvent
                End If
            End If
            Me.callbackCookie = Guid.Empty
        End Sub

        <OnSerializing> _
        Private Sub OnSerializing(ByVal context As StreamingContext)
            If ((Not Me.returnValueSignalEvent Is Nothing) AndAlso Not Me.responseSet) Then
                Me.callbackCookie = Guid.NewGuid
                SyncLock MethodMessage.syncRoot
                    MethodMessage.staticMethodMessageMap.Add(Me.callbackCookie, If(Me.previousMessage <>  Nothing , Me.previousMessage, Me))
                End SyncLock
            End If
        End Sub

        Private Sub PopulateIdentity(ByVal callContext As LogicalCallContext, ByVal identity As String)
            callContext.SetData("__identitycontext__", New IdentityContextData(identity))
        End Sub

        Public Sub SendException(ByVal exception As Exception)
            If (Me.returnValueSignalEvent Is Nothing) Then
                Throw New InvalidOperationException(String.Format(CultureInfo.CurrentCulture, SR.GetString("Error_InstanceDehydratedBeforeSendingResponse"), New Object(0  - 1) {}))
            End If
            If Not Me.responseSet Then
                Me.Exception = exception
                Me.returnValueSignalEvent.Set
                Me.responseSet = True
            End If
        End Sub

        Public Sub SendResponse(ByVal outArgs As ICollection)
            If (Me.returnValueSignalEvent Is Nothing) Then
                Throw New InvalidOperationException(String.Format(CultureInfo.CurrentCulture, SR.GetString("Error_InstanceDehydratedBeforeSendingResponse"), New Object(0  - 1) {}))
            End If
            If Not Me.responseSet Then
                Me.OutArgs = outArgs
                Me.returnValueSignalEvent.Set
                Me.responseSet = True
            End If
        End Sub

        Private Function System.Runtime.Remoting.Messaging.IMethodMessage.GetArg(ByVal argNum As Integer) As Object Implements IMethodMessage.GetArg
            Return Me.clonedArgs(argNum)
        End Function

        Private Function System.Runtime.Remoting.Messaging.IMethodMessage.GetArgName(ByVal index As Integer) As String Implements IMethodMessage.GetArgName
            Throw New NotImplementedException
        End Function

        Friend Function WaitForResponseMessage() As IMethodResponseMessage
            Me.returnValueSignalEvent.WaitOne
            Me.returnValueSignalEvent = Nothing
            Return Me
        End Function


        ' Properties
        Property Exception As Exception
            Public Get
                Return Me.exception
            End Get
            Private Set(ByVal value As Exception)
                If (Not Me.previousMessage Is Nothing) Then
                    Me.previousMessage.Exception = value
                End If
                Me.exception = value
            End Set
        End Property

        Property OutArgs As ICollection
            Public Get
                Return Me.outArgs
            End Get
            Private Set(ByVal value As ICollection)
                If (Not Me.previousMessage Is Nothing) Then
                    Me.previousMessage.OutArgs = value
                End If
                Me.outArgs = value
            End Set
        End Property

        Private ReadOnly Property System.Runtime.Remoting.Messaging.IMessage.Properties As IDictionary
            Get
                Throw New NotImplementedException
            End Get
        End Property

        Private ReadOnly Property System.Runtime.Remoting.Messaging.IMethodMessage.ArgCount As Integer
            Get
                Return Me.clonedArgs.Length
            End Get
        End Property

        Private ReadOnly Property System.Runtime.Remoting.Messaging.IMethodMessage.Args As Object()
            Get
                Return Me.clonedArgs
            End Get
        End Property

        Private ReadOnly Property System.Runtime.Remoting.Messaging.IMethodMessage.HasVarArgs As Boolean
            Get
                Throw New NotImplementedException
            End Get
        End Property

        Private ReadOnly Property System.Runtime.Remoting.Messaging.IMethodMessage.LogicalCallContext As LogicalCallContext
            Get
                Return Me.callContext
            End Get
        End Property

        Private ReadOnly Property System.Runtime.Remoting.Messaging.IMethodMessage.MethodBase As MethodBase
            Get
                Throw New NotImplementedException
            End Get
        End Property

        Private ReadOnly Property System.Runtime.Remoting.Messaging.IMethodMessage.MethodName As String
            Get
                Return Me.methodName
            End Get
        End Property

        Private ReadOnly Property System.Runtime.Remoting.Messaging.IMethodMessage.MethodSignature As Object
            Get
                Throw New NotImplementedException
            End Get
        End Property

        Private ReadOnly Property System.Runtime.Remoting.Messaging.IMethodMessage.TypeName As String
            Get
                Return Me.interfaceType.ToString
            End Get
        End Property

        Private ReadOnly Property System.Runtime.Remoting.Messaging.IMethodMessage.Uri As String
            Get
                Throw New NotImplementedException
            End Get
        End Property


        ' Fields
        <NonSerialized> _
        Private args As Object()
        Private callbackCookie As Guid
        Private callContext As LogicalCallContext
        Private clonedArgs As Object()
        Private exception As Exception
        <NonSerialized> _
        Private interfaceType As Type
        <NonSerialized> _
        Private methodName As String
        Private outArgs As ICollection
        <NonSerialized> _
        Private previousMessage As MethodMessage
        <NonSerialized> _
        Private responseSet As Boolean
        <NonSerialized> _
        Private returnValueSignalEvent As ManualResetEvent
        Private Shared singletonCallContext As LogicalCallContext
        Private Shared staticMethodMessageMap As Dictionary([Of] Guid, MethodMessage) = New Dictionary([Of] Guid, MethodMessage)
        Private Shared syncObject As Object = New Object
        Private Shared syncRoot As Object = New Object

        ' Nested Types
        Private NotInheritable Class CallContextProxy
            Inherits RealProxy
            ' Methods
            Friend Sub New(ByVal proxiedType As Type)
                MyBase.New(proxiedType)
            End Sub

            Public Overrides Function Invoke(ByVal msg As IMessage) As IMessage
                Dim mcm As IMethodCallMessage = [TryCast](msg,IMethodCallMessage)
                Me.callContext = [TryCast](mcm.LogicalCallContext.Clone,LogicalCallContext)
                Return New ReturnMessage(Nothing, Nothing, 0, mcm.LogicalCallContext, mcm)
            End Function


            ' Properties
            Friend ReadOnly Property CallContext As LogicalCallContext
                Get
                    Return Me.callContext
                End Get
            End Property


            ' Fields
            Private callContext As LogicalCallContext
        End Class
    End Class
End Namespace

